﻿#region --- License & Copyright Notice ---
/*
ConsoleFx CommandLine Processing Library

Copyright (c) 2006-2012 Jeevan James
All rights reserved.

The contents of this file are made available under the terms of the
Eclipse Public License v1.0 (the "License") which accompanies this
distribution, and is available at the following URL:
http://opensource.org/licenses/eclipse-1.0.txt

Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the License for
the specific language governing rights and limitations under the License.

By using this software in any fashion, you are agreeing to be bound by the
terms of the License.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;

using ConsoleFx.Parsers;
using ConsoleFx.Programs.Simple;
using ConsoleFx.Resources;

namespace ConsoleFx.Programs
{
    /// <summary>
    /// Base class for the simple and complex ConsoleProgram classes. It provides common base functionality
    /// for usage building and error handling
    /// </summary>
    public abstract class ConsoleProgramBase : Parser
    {
        private readonly Dictionary<Type, Delegate> _errorHandlers = new Dictionary<Type, Delegate>();
        private UsageBuilder _usageBuilder;

        #region Usage building
        public UsageBuilder UsageBuilder
        {
            get { return _usageBuilder; }
        }

        public void SetUsageBuilder<TUsageBuilder>()
            where TUsageBuilder : UsageBuilder
        {
            _usageBuilder = (UsageBuilder)Activator.CreateInstance(typeof(TUsageBuilder), this);
        }

        public void SetUsageBuilder(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            if (!typeof(UsageBuilder).IsAssignableFrom(type))
                throw new ArgumentException("The specified usage builder type does not derive from UsageBuilder", "type");
            _usageBuilder = (UsageBuilder)Activator.CreateInstance(type, this);
        }

        protected void DisplayUsage()
        {
            if (_usageBuilder == null)
                _usageBuilder = new SimpleResourceUsageBuilder(this);
            StringBuilder usageText = UsageBuilder.GenerateFullUsage();
            Console.WriteLine(usageText);
        } 
        #endregion

        #region Error handling
        //This method is public because it should also be called from a try-catch block in the program.
        //The built-in error handling code is only available from within the Run() method, which does
        //the bulk of the work, but the programmer still has to handle exceptions from the remaining
        //code (i.e. the code that sets up the command-line parsing parameters).
        public int HandleError(Exception ex)
        {
            FireBeforeError(ex);

            //Find the corresponding error handler and execute it (or use the default error handler).
            Delegate handler;
            if (_errorHandlers.TryGetValue(ex.GetType(), out handler))
                handler.DynamicInvoke(ex);
            else
                DefaultErrorHandler(ex);

            FireAfterError(ex);

            //Display usage, if needed
            if (Properties.Behavior.DisplayUsageOnError)
                DisplayUsage();

            //If the exception is a ConsoleFxException, we can use the exception's ErrorCode property
            //as the program's return value. Otherwise, we use the generic error code of -1.
            var consoleFxException = ex as ConsoleFxException;
            return consoleFxException != null ? consoleFxException.ErrorCode : -1;
        }

        /// <summary>
        /// Assigns a new error handler for a specified exception types. The handler is called whenever
        /// an exception of the specified type or it's derived types is thrown.
        /// </summary>
        /// <typeparam name="TException">The type of exception to handle</typeparam>
        /// <param name="handler">The method that handles the specified exception type</param>
        public void SetErrorHandler<TException>(ErrorHandler<TException> handler)
            where TException : Exception
        {
            _errorHandlers.Add(typeof(TException), handler);
        }

        /// <summary>
        /// Occurs before an error is handled an error handler. Allows you to specify pre-error actions
        /// that are common to all error handlers (like setting the console foreground color to red,
        /// for example).
        /// </summary>
        public event EventHandler<ErrorEventArgs> BeforeError;

        /// <summary>
        /// Occurs after an error has been handled by an error handler. Allows you to specify post-error
        /// actions that are common to all error handlers (like resetting the console foreground to
        /// default, for example).
        /// </summary>
        public event EventHandler<ErrorEventArgs> AfterError;

        //The default error handler, in case the framework cannot find a specific handler for an exception
        private static void DefaultErrorHandler(Exception ex)
        {
            var cfxException = ex as ConsoleFxException;

            //If the exception derives from ArgumentException or it derives from ConsoleFxException
            //and has a negative error code, treat it as an internal exception.
            if (ex is ArgumentException || (cfxException != null && cfxException.ErrorCode < 0))
            {
                Console.WriteLine(ConsoleProgramMessages.InternalErrorLine1);
                Console.WriteLine(ex.Message);
                Console.WriteLine(ConsoleProgramMessages.InternalErrorLine2);
            }
            else
                Console.WriteLine(ex.Message);
            ConsoleEx.WriteBlankLine();
        }

        private void FireBeforeError(Exception exception)
        {
            EventHandler<ErrorEventArgs> beforeError = BeforeError;
            if (beforeError != null)
                beforeError(this, new ErrorEventArgs(exception));
        }

        private void FireAfterError(Exception exception)
        {
            EventHandler<ErrorEventArgs> afterError = AfterError;
            if (afterError != null)
                afterError(this, new ErrorEventArgs(exception));
        }
        #endregion
    }
}
